const indexedDB =window.indexedDB

export default class DB {
  [x: string]: any;
  constructor (name: string, version: number, options: { storeName: string; option: { keyPath: string; autoIncrement: boolean }; tableKey: ({ key: string; unique: boolean; default: string; noIndex?: undefined } | { key: string; noIndex: boolean; default: string; unique?: undefined })[]; defaultData: { key: string; value: never[] }[] }[], open = true) {
    this.isPrepaing = true //  正在初始化中
    this.waitQueue = [] // 初始化中准备尝试调用的队列
    this._dbs = {}
    this._databaseName = name
    this._options = options
    this.currentStore = ''
    open && this.open(name, version, options)
  }

  open (name: string, version: number | undefined, options: any) {
    return new Promise((resolve, reject) => {
      if (this._dbs && this._dbs[name]) {
        resolve(this._dbs[name])
        return
      }
      const request = indexedDB.open(name, version)
      request.onupgradeneeded = (event:any) => {
        const db = event.target.result
        this._dbs[name] = db
        for (const store of options) {
          // 判断是否存在表
          if (!db.objectStoreNames.contains(store.storeName)) {
            var objectStore = db.createObjectStore(
              store.storeName,
              store.option
            )
            var list = store.tableKey.filter((item: { noIndex: any }) => !item.noIndex)
            for (const j of list) {
              objectStore.createIndex(j.key, j.keyPath || j.key, { unique: j.unique })
            }
            objectStore.transaction.oncomplete = () => {
              if (store.defaultData) {
                for (const item of store.defaultData) {
                  this.add(item)
                }
              }
            }
          }
        }
        resolve(db)
      }
      request.onsuccess = (event:any) => {
        // IDBDatabase
        const db = event.target.result
        // 缓存起来
        this._dbs[name] = db
        this.isPrepaing = false
        this.executeQueue()
        resolve(db)
      }
      request.onerror = event => {
        reject(event)
      }
    })
  }

  executeQueue () {
    if (this.waitQueue.length === 0) return
    while (this.waitQueue.length) {
      const call = this.waitQueue.shift()
      const db = this._dbs[this._databaseName]
      const objstore = db.transaction([this.currentStore], 'readwrite')
      call(objstore)
    }
  }

  async _getTransaction (storeName: any, version: any) {
    return new Promise((resolve) => {
      if (this.isPrepaing) { // 正在准备中
        this.waitQueue.push((obj: unknown) => resolve(obj))
        return
      }
      // 先从缓存获取
      const db = this._dbs[this._databaseName]
      resolve(db.transaction([storeName], 'readwrite'))
    })

    // let db
    // // 先从缓存获取
    // if (this._dbs[this._databaseName]) {
    //   db = this._dbs[this._databaseName]
    // } else {
    //   db = await this.open(this._databaseName, version)
    // }
    // return db.transaction([storeName], 'readwrite')
  }

  async _getObjectStore (storeName: any, version?: undefined) {
    const transaction:any = await this._getTransaction(storeName, version)
    return transaction.objectStore(storeName)
  }

  // 获取一个store
  collection (storeName: string) {
    this.currentStore = storeName
    // this._getObjectStore(storeName, version);
    return this
  }

  add (data: { [x: string]: any }) {
    return new Promise((resolve, reject) => {
      this._getObjectStore(this.currentStore).then(objectStore => {
        const table = this._options.find((item: { storeName: any }) => item.storeName === this.currentStore)
        table.tableKey.forEach((item: { noData: any; default: any; key: string | number }) => {
          if (item.noData) return true
          const def = item.default
          if (typeof def !== 'undefined' && typeof data[item.key] === 'undefined') {
            data[item.key] = typeof def === 'function' ? def() : def
          }
        })

        const request = objectStore.add(data)
        request.onsuccess = function (event: { target: { result: unknown } }) {
          resolve(event.target.result)
        }
        request.onerror = (event: any) => {
          reject(event)
        }
      })
    })
  }

  get (id: any) {
    return new Promise((resolve, reject) => {
      this._getObjectStore(this.currentStore).then(objectStore => {
        const request = objectStore.get(id)
        request.onsuccess = function (event: { target: { result: unknown } }) {
          resolve(event.target.result)
        }
        request.onerror = (event: any) => {
          reject(event)
        }
      })
    })
  }
  getAll (data?: any) {
    return new Promise((resolve, reject) => {
      this._getObjectStore(this.currentStore).then(objectStore => {
        let request
        if (data && data.key) {
          const index = objectStore.index(data.key)
          request = index.getAll(data.value)
        } else {
          request = objectStore.getAll()
        }
        request.onsuccess = function (event: { target: { result: unknown } }) {
          resolve(event.target.result)
        }
        request.onerror = (event: any) => {
          reject(event)
        }
      })
    })
  }
  getCount (data: { key: any; value: any }) {
    return new Promise((resolve, reject) => {
      this._getObjectStore(this.currentStore).then(objectStore => {
        const index = objectStore.index(data.key)
        const request = index.count(data.value)
        request.onsuccess = (event: { target: { result: unknown } }) => {
          resolve(event.target.result)
        }
        request.onerror = (e: any) => {
          reject(e)
        }
      })
    })
  }
  delete (data: any) {
    return new Promise((resolve, reject) => {
      this._getObjectStore(this.currentStore).then(objectStore => {
        const request = objectStore.delete(data)
        request.onsuccess = function (event: { target: { result: unknown } }) {
          resolve(event.target.result)
        }
        request.onerror = (event: any) => {
          reject(event)
        }
      })
    })
  }
  deleteAll (indexName: any, indexValue: any) {
    return new Promise((resolve, reject) => {
      this._getObjectStore(this.currentStore).then(objectStore => {
        const request = objectStore.index(indexName) // 索引对象
          .openCursor(IDBKeyRange.only(indexValue)) // 指针对象
        request.onsuccess = function (e: { target: { result: any } }) {
          const cursor = e.target.result
          let deleteRequest
          if (cursor) {
            deleteRequest = cursor.delete() // 请求删除当前项
            deleteRequest.onerror = (event: any) => {
              reject(event)
            }
            deleteRequest.onsuccess = function () {
              resolve('')
            }
            cursor.continue()
          }
        }
        request.onerror = (event: any) => {
          reject(event)
        }
      })
    })
  }
  clear (data: any) {
    return new Promise((resolve, reject) => {
      this._getObjectStore(this.currentStore).then(objectStore => {
        const request = objectStore.clear(data)
        request.onsuccess = function (event: { target: { result: unknown } }) {
          resolve(event.target.result)
        }
        request.onerror = (event: any) => {
          reject(event)
        }
      })
    })
  }

  put (data: any) {
    return new Promise(async (resolve, reject) => {
      this.get(data.id || data.key).then((res :any) => {
        const params = Object.assign(res, data)
        this._getObjectStore(this.currentStore).then(objectStore => {
          const request = objectStore.put(params)
          request.onsuccess = function (event: { target: { result: unknown } }) {
            resolve(event.target.result)
          }
          request.onerror = (event: any) => {
            reject(event)
          }
        })
      }).catch(reject)
    })
  }

  find (start: any, end: any, startInclude: boolean | undefined, endInclude: boolean | undefined) {
    return new Promise((resolve, reject) => {
      this._getObjectStore(this.currentStore).then(objectStore => {
        const request = objectStore.openCursor(
          IDBKeyRange.bound(start, end, startInclude, endInclude)
        )
        request.onsuccess = function (event: { target: { result: unknown } }) {
          resolve(event.target.result)
        }
        request.onerror = (event: any) => {
          reject(event)
        }
      })
    })
  }
}
